/*
 * gistfuncs.c
 *		用于调查 GiST 索引内容的函数
 *
 * Copyright (c) 2014-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *		contrib/pageinspect/gistfuncs.c
 */
#include "postgres.h"

#include "access/gist.h"
#include "access/gist_private.h"
#include "access/htup.h"
#include "access/relation.h"
#include "catalog/namespace.h"
#include "catalog/pg_am_d.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "pageinspect.h"
#include "storage/itemptr.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/pg_lsn.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
#include "utils/ruleutils.h"
#include "utils/varlena.h"

PG_FUNCTION_INFO_V1(gist_page_opaque_info);
PG_FUNCTION_INFO_V1(gist_page_items);
PG_FUNCTION_INFO_V1(gist_page_items_bytea);

#define IS_GIST(r) ((r)->rd_rel->relam == GIST_AM_OID)

#define ItemPointerGetDatum(X)	 PointerGetDatum(X)


static Page fc_verify_gist_page(bytea *fc_raw_page);

/*
 * 验证给定的bytea是否包含GIST页面，否则就会失败。
 * 返回对该页面的指针。
 */
static Page fc_verify_gist_page(bytea *fc_raw_page)
{
	Page		fc_page = get_page_from_raw(fc_raw_page);
	GISTPageOpaque fc_opaq;

	if (PageIsNew(fc_page))
		return fc_page;

	/* 验证特殊空间的大小是否符合预期 */
	if (PageGetSpecialSize(fc_page) != MAXALIGN(sizeof(GISTPageOpaqueData)))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("input page is not a valid %s page", "GiST"),
				 errdetail("Expected special size %d, got %d.",
						   (int) MAXALIGN(sizeof(GISTPageOpaqueData)),
						   (int) PageGetSpecialSize(fc_page))));

	fc_opaq = GistPageGetOpaque(fc_page);
	if (fc_opaq->gist_page_id != GIST_PAGE_ID)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("input page is not a valid %s page", "GiST"),
				 errdetail("Expected %08x, got %08x.",
						   GIST_PAGE_ID,
						   fc_opaq->gist_page_id)));

	return fc_page;
}

Datum gist_page_opaque_info(PG_FUNCTION_ARGS)
{
	bytea	   *fc_raw_page = PG_GETARG_BYTEA_P(0);
	TupleDesc	fc_tupdesc;
	Page		fc_page;
	HeapTuple	fc_resultTuple;
	Datum		fc_values[4];
	bool		fc_nulls[4];
	Datum		fc_flags[16];
	int			fc_nflags = 0;
	uint16		fc_flagbits;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use raw page functions")));

	fc_page = fc_verify_gist_page(fc_raw_page);

	if (PageIsNew(fc_page))
		PG_RETURN_NULL();

	/* 为我们的结果类型构建一个元组描述符 */
	if (get_call_result_type(fcinfo, NULL, &fc_tupdesc) != TYPEFUNC_COMPOSITE)
		elog(ERROR, "return type must be a row type");

	/* 将标志位掩码转换为一组可读名称 */
	fc_flagbits = GistPageGetOpaque(fc_page)->flags;
	if (fc_flagbits & F_LEAF)
		fc_flags[fc_nflags++] = CStringGetTextDatum("leaf");
	if (fc_flagbits & F_DELETED)
		fc_flags[fc_nflags++] = CStringGetTextDatum("deleted");
	if (fc_flagbits & F_TUPLES_DELETED)
		fc_flags[fc_nflags++] = CStringGetTextDatum("tuples_deleted");
	if (fc_flagbits & F_FOLLOW_RIGHT)
		fc_flags[fc_nflags++] = CStringGetTextDatum("follow_right");
	if (fc_flagbits & F_HAS_GARBAGE)
		fc_flags[fc_nflags++] = CStringGetTextDatum("has_garbage");
	fc_flagbits &= ~(F_LEAF | F_DELETED | F_TUPLES_DELETED | F_FOLLOW_RIGHT | F_HAS_GARBAGE);
	if (fc_flagbits)
	{
		/* 我们不识别的任何标志以十六进制打印 */
		fc_flags[fc_nflags++] = DirectFunctionCall1(to_hex32, Int32GetDatum(fc_flagbits));
	}

	memset(fc_nulls, 0, sizeof(fc_nulls));

	fc_values[0] = LSNGetDatum(PageGetLSN(fc_page));
	fc_values[1] = LSNGetDatum(GistPageGetNSN(fc_page));
	fc_values[2] = Int64GetDatum(GistPageGetOpaque(fc_page)->rightlink);
	fc_values[3] = PointerGetDatum(construct_array(fc_flags, fc_nflags,
												TEXTOID,
												-1, false, TYPALIGN_INT));

	/* 构建并返回结果元组。 */
	fc_resultTuple = heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);

	return HeapTupleGetDatum(fc_resultTuple);
}

Datum gist_page_items_bytea(PG_FUNCTION_ARGS)
{
	bytea	   *fc_raw_page = PG_GETARG_BYTEA_P(0);
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	Page		fc_page;
	OffsetNumber fc_offset;
	OffsetNumber fc_maxoff = InvalidOffsetNumber;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use raw page functions")));

	InitMaterializedSRF(fcinfo, 0);

	fc_page = fc_verify_gist_page(fc_raw_page);

	if (PageIsNew(fc_page))
		PG_RETURN_NULL();

	/* 避免在已删除页面上进行错误的PageGetMaxOffsetNumber()调用 */
	if (GistPageIsDeleted(fc_page))
		elog(NOTICE, "page is deleted");
	else
		fc_maxoff = PageGetMaxOffsetNumber(fc_page);

	for (fc_offset = FirstOffsetNumber;
		 fc_offset <= fc_maxoff;
		 fc_offset++)
	{
		Datum		fc_values[5];
		bool		fc_nulls[5];
		ItemId		fc_id;
		IndexTuple	fc_itup;
		bytea	   *fc_tuple_bytea;
		int			fc_tuple_len;

		fc_id = PageGetItemId(fc_page, fc_offset);

		if (!ItemIdIsValid(fc_id))
			elog(ERROR, "invalid ItemId");

		fc_itup = (IndexTuple) PageGetItem(fc_page, fc_id);
		fc_tuple_len = IndexTupleSize(fc_itup);

		memset(fc_nulls, 0, sizeof(fc_nulls));

		fc_values[0] = DatumGetInt16(fc_offset);
		fc_values[1] = ItemPointerGetDatum(&fc_itup->t_tid);
		fc_values[2] = Int32GetDatum((int) IndexTupleSize(fc_itup));

		fc_tuple_bytea = (bytea *) palloc(fc_tuple_len + VARHDRSZ);
		SET_VARSIZE(fc_tuple_bytea, fc_tuple_len + VARHDRSZ);
		memcpy(VARDATA(fc_tuple_bytea), fc_itup, fc_tuple_len);
		fc_values[3] = BoolGetDatum(ItemIdIsDead(fc_id));
		fc_values[4] = PointerGetDatum(fc_tuple_bytea);

		tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc, fc_values, fc_nulls);
	}

	return (Datum) 0;
}

Datum gist_page_items(PG_FUNCTION_ARGS)
{
	bytea	   *fc_raw_page = PG_GETARG_BYTEA_P(0);
	Oid			fc_indexRelid = PG_GETARG_OID(1);
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	Relation	fc_indexRel;
	TupleDesc	fc_tupdesc;
	Page		fc_page;
	uint16		fc_flagbits;
	bits16		fc_printflags = 0;
	OffsetNumber fc_offset;
	OffsetNumber fc_maxoff = InvalidOffsetNumber;
	char	   *fc_index_columns;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use raw page functions")));

	InitMaterializedSRF(fcinfo, 0);

	/* 打开关系 */
	fc_indexRel = index_open(fc_indexRelid, AccessShareLock);

	if (!IS_GIST(fc_indexRel))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("\"%s\" is not a %s index",
						RelationGetRelationName(fc_indexRel), "GiST")));

	fc_page = fc_verify_gist_page(fc_raw_page);

	if (PageIsNew(fc_page))
	{
		index_close(fc_indexRel, AccessShareLock);
		PG_RETURN_NULL();
	}

	fc_flagbits = GistPageGetOpaque(fc_page)->flags;

	/*
	 * 处理叶子页面时添加包含的属性，
	 * 对于非叶子页面则丢弃，因为这些仅包含键属性的数据。
	 */
	fc_printflags |= RULE_INDEXDEF_PRETTY;
	if (fc_flagbits & F_LEAF)
	{
		fc_tupdesc = RelationGetDescr(fc_indexRel);
	}
	else
	{
		fc_tupdesc = CreateTupleDescCopy(RelationGetDescr(fc_indexRel));
		fc_tupdesc->natts = IndexRelationGetNumberOfKeyAttributes(fc_indexRel);
		fc_printflags |= RULE_INDEXDEF_KEYS_ONLY;
	}

	fc_index_columns = pg_get_indexdef_columns_extended(fc_indexRelid,
													 fc_printflags);

	/* 避免在已删除页面上进行错误的PageGetMaxOffsetNumber()调用 */
	if (GistPageIsDeleted(fc_page))
		elog(NOTICE, "page is deleted");
	else
		fc_maxoff = PageGetMaxOffsetNumber(fc_page);

	for (fc_offset = FirstOffsetNumber;
		 fc_offset <= fc_maxoff;
		 fc_offset++)
	{
		Datum		fc_values[5];
		bool		fc_nulls[5];
		ItemId		fc_id;
		IndexTuple	fc_itup;
		Datum		fc_itup_values[INDEX_MAX_KEYS];
		bool		fc_itup_isnull[INDEX_MAX_KEYS];
		StringInfoData fc_buf;
		int			fc_i;

		fc_id = PageGetItemId(fc_page, fc_offset);

		if (!ItemIdIsValid(fc_id))
			elog(ERROR, "invalid ItemId");

		fc_itup = (IndexTuple) PageGetItem(fc_page, fc_id);

		index_deform_tuple(fc_itup, fc_tupdesc,
						   fc_itup_values, fc_itup_isnull);

		memset(fc_nulls, 0, sizeof(fc_nulls));

		fc_values[0] = DatumGetInt16(fc_offset);
		fc_values[1] = ItemPointerGetDatum(&fc_itup->t_tid);
		fc_values[2] = Int32GetDatum((int) IndexTupleSize(fc_itup));
		fc_values[3] = BoolGetDatum(ItemIdIsDead(fc_id));

		if (fc_index_columns)
		{
			initStringInfo(&fc_buf);
			appendStringInfo(&fc_buf, "(%s)=(", fc_index_columns);

			/* 大部分内容是从record_out()复制过来的。 */
			for (fc_i = 0; fc_i < fc_tupdesc->natts; fc_i++)
			{
				char	   *fc_value;
				char	   *fc_tmp;
				bool		fc_nq = false;

				if (fc_itup_isnull[fc_i])
					fc_value = "null";
				else
				{
					Oid			fc_foutoid;
					bool		fc_typisvarlena;
					Oid			fc_typoid;

					fc_typoid = fc_tupdesc->attrs[fc_i].atttypid;
					getTypeOutputInfo(fc_typoid, &fc_foutoid, &fc_typisvarlena);
					fc_value = OidOutputFunctionCall(fc_foutoid, fc_itup_values[fc_i]);
				}

				if (fc_i == IndexRelationGetNumberOfKeyAttributes(fc_indexRel))
					appendStringInfoString(&fc_buf, ") INCLUDE (");
				else if (fc_i > 0)
					appendStringInfoString(&fc_buf, ", ");

				/* 检查我们是否需要为该值添加双引号 */
				fc_nq = (fc_value[0] == '\0');	/* 强制对空字符串使用引号 */
				for (fc_tmp = fc_value; *fc_tmp; fc_tmp++)
				{
					char		fc_ch = *fc_tmp;

					if (fc_ch == '"' || fc_ch == '\\' ||
						fc_ch == '(' || fc_ch == ')' || fc_ch == ',' ||
						isspace((unsigned char) fc_ch))
					{
						fc_nq = true;
						break;
					}
				}

				/* 并输出字符串 */
				if (fc_nq)
					appendStringInfoCharMacro(&fc_buf, '"');
				for (fc_tmp = fc_value; *fc_tmp; fc_tmp++)
				{
					char		fc_ch = *fc_tmp;

					if (fc_ch == '"' || fc_ch == '\\')
						appendStringInfoCharMacro(&fc_buf, fc_ch);
					appendStringInfoCharMacro(&fc_buf, fc_ch);
				}
				if (fc_nq)
					appendStringInfoCharMacro(&fc_buf, '"');
			}

			appendStringInfoChar(&fc_buf, ')');

			fc_values[4] = CStringGetTextDatum(fc_buf.data);
			fc_nulls[4] = false;
		}
		else
		{
			fc_values[4] = (Datum) 0;
			fc_nulls[4] = true;
		}

		tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc, fc_values, fc_nulls);
	}

	relation_close(fc_indexRel, AccessShareLock);

	return (Datum) 0;
}
